<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML
><HEAD
><TITLE
>Writing Scripts</TITLE
><META
NAME="GENERATOR"
CONTENT="Modular DocBook HTML Stylesheet Version 1.7"><LINK
REL="HOME"
TITLE="Advanced Bash-Scripting Guide"
HREF="index.html"><LINK
REL="UP"
TITLE="Exercises"
HREF="exercises.html"><LINK
REL="PREVIOUS"
TITLE="Analyzing Scripts"
HREF="scriptanalysis.html"><LINK
REL="NEXT"
TITLE="Revision History"
HREF="revisionhistory.html"></HEAD
><BODY
CLASS="SECT1"
BGCOLOR="#FFFFFF"
TEXT="#000000"
LINK="#0000FF"
VLINK="#840084"
ALINK="#0000FF"
><DIV
CLASS="NAVHEADER"
><TABLE
SUMMARY="Header navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TH
COLSPAN="3"
ALIGN="center"
>Advanced Bash-Scripting Guide: </TH
></TR
><TR
><TD
WIDTH="10%"
ALIGN="left"
VALIGN="bottom"
><A
HREF="scriptanalysis.html"
ACCESSKEY="P"
>Prev</A
></TD
><TD
WIDTH="80%"
ALIGN="center"
VALIGN="bottom"
>Appendix O. Exercises</TD
><TD
WIDTH="10%"
ALIGN="right"
VALIGN="bottom"
><A
HREF="revisionhistory.html"
ACCESSKEY="N"
>Next</A
></TD
></TR
></TABLE
><HR
ALIGN="LEFT"
WIDTH="100%"></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="WRITINGSCRIPTS"
></A
>O.2. Writing Scripts</H1
><P
><A
NAME="WRITINGSCRIPTS1"
></A
></P
><P
>Write a script to carry out each of the following tasks.</P
><P
></P
><DIV
CLASS="VARIABLELIST"
><P
><B
><A
NAME="EXEASY1"
></A
>EASY</B
></P
><DL
><DT
><B
CLASS="COMMAND"
>Self-reproducing Script</B
></DT
><DD
><P
>Write a script that backs itself up, that is, copies
	      itself to a file named <TT
CLASS="FILENAME"
>backup.sh</TT
>.</P
><P
>Hint: Use the <A
HREF="basic.html#CATREF"
>cat</A
> command
	      and the appropriate <A
HREF="othertypesv.html#SCRNAMEPARAM"
>positional
	      parameter</A
>.</P
></DD
><DT
><B
CLASS="COMMAND"
>Home Directory Listing</B
></DT
><DD
><P
>Perform a recursive directory listing on the user's home
	      directory and save the information to a file. Compress
	      the file, have the script prompt the user to insert
	      a USB flash drive, then press <B
CLASS="KEYCAP"
>ENTER</B
>.
              Finally, save the file to the flash drive after making
              certain the flash drive has properly mounted by parsing
              the output of <A
HREF="system.html#DFREF"
>df</A
>. Note that
              the flash drive must be <I
CLASS="FIRSTTERM"
>unmounted</I
>
              before it is removed.</P
></DD
><DT
><B
CLASS="COMMAND"
>Converting  <A
HREF="loops1.html#FORLOOPREF1"
>for</A
>
	    loops to <A
HREF="loops1.html#WHILELOOPREF"
>while</A
> and <A
HREF="loops1.html#UNTILLOOPREF"
>until</A
> loops</B
></DT
><DD
><P
>Convert the <I
CLASS="FIRSTTERM"
>for loops</I
> in <A
HREF="loops1.html#EX22"
>Example 11-1</A
> to <I
CLASS="FIRSTTERM"
>while
	      loops</I
>. Hint: store the data in an <A
HREF="arrays.html#ARRAYREF"
>array</A
> and step through the array
	      elements.</P
><P
>Having already done the <SPAN
CLASS="QUOTE"
>"heavy lifting,"</SPAN
>
	      now convert the loops in the example to <I
CLASS="FIRSTTERM"
> until
	      loops</I
>.</P
></DD
><DT
><B
CLASS="COMMAND"
>Changing the line spacing of a text file</B
></DT
><DD
><P
>Write a script that reads each line of a target file, then
	      writes the line back to <TT
CLASS="FILENAME"
>stdout</TT
>, but with
	      an extra blank line following. This has the effect of
	      <EM
>double-spacing</EM
> the file.</P
><P
>Include all necessary code to check whether the script
	      gets the necessary command-line argument (a filename),
	      and whether the specified file exists.</P
><P
>When the script runs correctly, modify it to
	      <EM
>triple-space</EM
> the target file.</P
><P
>Finally, write a script to remove all blank lines from
	      the target file, <EM
>single-spacing</EM
> it.</P
></DD
><DT
><B
CLASS="COMMAND"
>Backwards Listing</B
></DT
><DD
><P
>Write a script that echoes itself to
	      <TT
CLASS="FILENAME"
>stdout</TT
>, but
	      <EM
>backwards</EM
>.</P
></DD
><DT
><B
CLASS="COMMAND"
>Automatically Decompressing Files</B
></DT
><DD
><P
>Given a list of filenames as input, this script
	      queries each target file (parsing the output of the
	      <A
HREF="filearchiv.html#FILEREF"
>file</A
> command) for
	      the type of compression used on it. Then the script
	      automatically invokes the appropriate decompression command
	      (<B
CLASS="COMMAND"
>gunzip</B
>, <B
CLASS="COMMAND"
>bunzip2</B
>,
	      <B
CLASS="COMMAND"
>unzip</B
>, <B
CLASS="COMMAND"
>uncompress</B
>,
	      or whatever). If a target file is not compressed, the
	      script emits a warning message, but takes no other action
	      on that particular file.</P
></DD
><DT
><B
CLASS="COMMAND"
>Unique System ID</B
></DT
><DD
><P
>Generate a <SPAN
CLASS="QUOTE"
>"unique"</SPAN
> 6-digit hexadecimal
	      identifier for your computer. Do <EM
>not</EM
>
	      use the flawed <A
HREF="system.html#HOSTIDREF"
>hostid</A
>
	      command. Hint: <B
CLASS="COMMAND"
><A
HREF="filearchiv.html#MD5SUMREF"
>md5sum</A
>
	      <A
HREF="files.html#DATAFILESREF1"
><TT
CLASS="FILENAME"
>/etc/passwd</TT
></A
></B
>,
	      then select the first 6 digits of output.</P
></DD
><DT
><B
CLASS="COMMAND"
>Backup</B
></DT
><DD
><P
>Archive as a <SPAN
CLASS="QUOTE"
>"tarball"</SPAN
>
	      (<TT
CLASS="FILENAME"
>*.tar.gz</TT
> file) all the files
	      in your home directory tree
	      (<TT
CLASS="FILENAME"
>/home/your-name</TT
>) that have
	      been modified in the last 24 hours. Hint: use <A
HREF="moreadv.html#FINDREF"
>find</A
>.</P
><P
>Optional: you may use this as the basis of a
              <I
CLASS="FIRSTTERM"
>backup</I
> script.</P
></DD
><DT
><B
CLASS="COMMAND"
>Checking whether a process is still running</B
></DT
><DD
><P
>Given a <A
HREF="special-chars.html#PROCESSIDREF"
>process ID</A
>
	      (<I
CLASS="FIRSTTERM"
>PID</I
>) as an argument, this script
	      will check, at user-specified intervals, whether
	      the given process is still running. You may use
	      the <A
HREF="system.html#PPSSREF"
>ps</A
> and <A
HREF="timedate.html#SLEEPREF"
>sleep</A
> commands.</P
></DD
><DT
><B
CLASS="COMMAND"
>Primes</B
></DT
><DD
><P
>Print (to <TT
CLASS="FILENAME"
>stdout</TT
>) all
		      prime numbers between 60000 and 63000. The output
		      should be nicely formatted in columns (hint:
		      use <A
HREF="internal.html#PRINTFREF"
>printf</A
>).</P
></DD
><DT
><B
CLASS="COMMAND"
>Lottery Numbers</B
></DT
><DD
><P
>One type of lottery involves picking five
	      different numbers, in the range of 1 - 50. Write a
	      script that generates five pseudorandom numbers in this
	      range, <EM
>with no duplicates</EM
>. The
	      script will give the option of echoing the numbers to
	      <TT
CLASS="FILENAME"
>stdout</TT
> or saving them to a file,
	      along with the date and time the particular number set
	      was generated. (If your script consistently generates
	      <EM
>winning</EM
> lottery numbers, then you
	      can retire on the proceeds and leave shell scripting to
	      those of us who have to work for a living.)</P
></DD
></DL
></DIV
><P
></P
><DIV
CLASS="VARIABLELIST"
><P
><B
><A
NAME="EXMEDIUM1"
></A
>INTERMEDIATE</B
></P
><DL
><DT
><B
CLASS="COMMAND"
>Integer or String</B
></DT
><DD
><P
>Write a script <A
HREF="functions.html#FUNCTIONREF"
>function</A
>
	      that determines if an argument passed to it is an integer
	      or a string. The function will return TRUE (0) if
	      passed an integer, and FALSE (1) if passed a string.</P
><P
>Hint: What does the following expression return
	      when <TT
CLASS="VARNAME"
>$1</TT
> is <EM
>not</EM
>
	      an integer?</P
><P
><TT
CLASS="VARNAME"
>expr $1 + 0</TT
></P
></DD
><DT
><B
CLASS="COMMAND"
><A
HREF="special-chars.html#ASCIIDEF"
>ASCII</A
>
              to Integer</B
></DT
><DD
><P
>The <I
CLASS="FIRSTTERM"
>atoi</I
> function in
                <B
CLASS="COMMAND"
>C</B
> converts a string character to
                an integer. Write a shell script function that performs
		the same operation. Likewise, write a shell script function
		that does the inverse, mirroring the <B
CLASS="COMMAND"
>C</B
>
		<I
CLASS="FIRSTTERM"
>itoa</I
> function which converts an
		integer into an ASCII character.</P
></DD
><DT
><B
CLASS="COMMAND"
>Managing Disk Space</B
></DT
><DD
><P
>List, one at a time, all files larger than 100K in
	      the <TT
CLASS="FILENAME"
>/home/username</TT
>
	      directory tree. Give the user the option to delete or
	      compress the file, then proceed to show the next one. Write
	      to a logfile the names of all deleted files and the
	      deletion times.</P
></DD
><DT
><B
CLASS="COMMAND"
>Banner</B
></DT
><DD
><P
>Simulate the functionality of the deprecated <A
HREF="extmisc.html#BANNERREF"
>banner</A
> command in a script.</P
></DD
><DT
><B
CLASS="COMMAND"
>Removing Inactive Accounts</B
></DT
><DD
><P
>Inactive accounts on a network server waste disk space and may
	      become a security risk. Write an administrative script
	      (to be invoked by <I
CLASS="FIRSTTERM"
>root</I
> or the <A
HREF="system.html#CRONREF"
>cron daemon</A
>) that checks
	      for and deletes user accounts that have not been accessed
	      within the last 90 days.</P
></DD
><DT
><B
CLASS="COMMAND"
>Enforcing Disk Quotas</B
></DT
><DD
><P
>Write a script for a multi-user system that checks users'
	      disk usage. If a user surpasses a preset limit
	      (500 MB, for example) in her <TT
CLASS="FILENAME"
>/home/username</TT
>
	      directory, then the script automatically sends her a
	      <SPAN
CLASS="QUOTE"
>"pigout"</SPAN
> warning e-mail.</P
><P
>The
	      script will use the <A
HREF="system.html#DUREF"
>du</A
>
	      and <A
HREF="communications.html#COMMMAIL1"
>mail</A
> commands. As
	      an option, it will allow setting and enforcing quotas
	      using the <A
HREF="system.html#QUOTAREF"
>quota</A
> and <A
HREF="system.html#SETQUOTAREF"
>setquota</A
> commands.</P
></DD
><DT
><B
CLASS="COMMAND"
>Logged in User Information</B
></DT
><DD
><P
>For all logged in users, show their real names and the time
              and date of their last login.</P
><P
>Hint: use <A
HREF="system.html#WHOREF"
>who</A
>,
	      <A
HREF="system.html#LASTLOGREF"
>lastlog</A
>,
	      and parse <A
HREF="files.html#DATAFILESREF1"
><TT
CLASS="FILENAME"
>/etc/passwd</TT
></A
>.</P
></DD
><DT
><B
CLASS="COMMAND"
>Safe Delete</B
></DT
><DD
><P
>Implement, as a script, a <SPAN
CLASS="QUOTE"
>"safe"</SPAN
> delete
	      command, <TT
CLASS="FILENAME"
>sdel.sh</TT
>. Filenames passed as
	      command-line arguments to this script are not deleted,
	      but instead <A
HREF="filearchiv.html#GZIPREF"
>gzipped</A
>
	      if not already compressed (use <A
HREF="filearchiv.html#FILEREF"
>file</A
> to check), then moved
	      to a <TT
CLASS="FILENAME"
>~/TRASH</TT
>
	      directory. Upon invocation, the script checks the <TT
CLASS="FILENAME"
>~/TRASH</TT
> directory for files
	      older than 48 hours and <A
HREF="basic.html#RMREF"
>permanently
	      deletes</A
> them. (An better alternative might be to
	      have a second script handle this, periodically invoked
	      by the <A
HREF="system.html#CRONREF"
>cron daemon</A
>.)</P
><P
><EM
>Extra credit:</EM
> Write the script
	      so it can handle files and directories <A
HREF="basic.html#RMRECURS"
>recursively</A
>. This would give it
	      the capability of <SPAN
CLASS="QUOTE"
>"safely deleting"</SPAN
> entire
	      directory structures.</P
></DD
><DT
><B
CLASS="COMMAND"
>Making Change</B
></DT
><DD
><P
>What is the most efficient way to make change for $1.68,
	      using only coins in common circulations (up to 25c)? It's
	      6 quarters, 1 dime, a nickel, and three cents.</P
><P
>Given any arbitrary command-line input in dollars and
	      cents ($*.??), calculate the change, using the minimum
	      number of coins. If your home country is not the United
	      States, you may use your local currency units instead. The
	      script will need to parse the command-line input, then
	      change it to multiples of the smallest monetary unit (cents
	      or whatever). Hint: look at <A
HREF="complexfunct.html#EX61"
>Example 24-8</A
>.</P
></DD
><DT
><B
CLASS="COMMAND"
>Quadratic Equations</B
></DT
><DD
><P
>Solve a <I
CLASS="FIRSTTERM"
>quadratic</I
> equation of the form
	      <TT
CLASS="PARAMETER"
><I
>Ax^2 + Bx + C = 0</I
></TT
>. Have a script take
	      as arguments the coefficients, <TT
CLASS="USERINPUT"
><B
>A</B
></TT
>,
	      <TT
CLASS="USERINPUT"
><B
>B</B
></TT
>, and <TT
CLASS="USERINPUT"
><B
>C</B
></TT
>,
	      and return the solutions to five decimal places.</P
><P
>Hint: pipe the coefficients to <A
HREF="mathc.html#BCREF"
>bc</A
>, using the well-known formula,
	      <TT
CLASS="PARAMETER"
><I
>x = ( -B +/- sqrt( B^2 - 4AC ) ) / 2A</I
></TT
>.</P
></DD
><DT
><B
CLASS="COMMAND"
>Table of Logarithms</B
></DT
><DD
><P
>Using the <A
HREF="mathc.html#BCREF"
>bc</A
> and <A
HREF="internal.html#PRINTFREF"
>printf</A
> commands, print out a
	      nicely-formatted table of eight-place natural logarithms
	      in the interval between 0.00 and 100.00, in steps of
	      .01.</P
><P
>Hint: <I
CLASS="FIRSTTERM"
>bc</I
> requires the
	      <TT
CLASS="OPTION"
>-l</TT
> option to load the math library.</P
></DD
><DT
><B
CLASS="COMMAND"
>Unicode Table</B
></DT
><DD
><P
>Using <A
HREF="asciitable.html#ASCIISH"
>Example T-1</A
> as a template,
                 write a script that prints to a file a complete
                 <A
HREF="bashver4.html#UNICODEREF"
>Unicode</A
> table.</P
><P
>Hint: Use the <TT
CLASS="OPTION"
>-e</TT
> option to
		 <A
HREF="internal.html#ECHOREF"
>echo</A
>:
		 <B
CLASS="COMMAND"
>echo -e '\uXXXX'</B
>, where
		 <TT
CLASS="REPLACEABLE"
><I
>XXXX</I
></TT
>
		 is the Unicode numerical character designation.
		 This requires <A
HREF="bashver4.html#BASH42"
>version 4.2</A
>
		 or later of Bash.</P
></DD
><DT
><B
CLASS="COMMAND"
>Sum of Matching Numbers</B
></DT
><DD
><P
>Find the sum of all five-digit numbers (in the range
	      10000 - 99999) containing <EM
>exactly two</EM
>
	      out of the following set of digits: { 4, 5, 6 }. These may
	      repeat within the same number, and if so, they count once
	      for each occurrence.</P
><P
>Some examples of <I
CLASS="FIRSTTERM"
>matching numbers</I
> are
	      42057, 74638, and 89515.</P
></DD
><DT
><B
CLASS="COMMAND"
>Lucky Numbers</B
></DT
><DD
><P
>A <I
CLASS="FIRSTTERM"
>lucky number</I
> is one whose
	      individual digits add up to 7, in successive additions. For
	      example, 62431 is a <I
CLASS="FIRSTTERM"
>lucky number</I
>
	      (6 + 2 + 4 + 3 + 1 = 16, 1 + 6 = 7). Find all the
	      <I
CLASS="FIRSTTERM"
>lucky numbers</I
> between 1000 and
	      10000.</P
></DD
><DT
><B
CLASS="COMMAND"
>Craps</B
></DT
><DD
><P
>Borrowing the ASCII graphics from <A
HREF="contributed-scripts.html#PETALS"
>Example A-40</A
>,
	      write a script that plays the well-known gambling game of
	      <I
CLASS="FIRSTTERM"
>craps</I
>. The script will accept bets
	      from one or more players, roll the dice, and keep track of
	      wins and losses, as well as of each player's bankroll.</P
></DD
><DT
><B
CLASS="COMMAND"
>Tic-tac-toe</B
></DT
><DD
><P
>Write a script that plays the child's game of
	      <I
CLASS="FIRSTTERM"
>tic-tac-toe</I
> against a human
	      player. The script will let the human choose whether
	      to take the first move. The script will follow
	      an optimal strategy, and therefore never lose. To simplify
	      matters, you may use ASCII graphics:</P
><P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="90%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
>   o | x |
   ----------
     | x |
   ----------
     | o |
     
   Your move, human (row, column)?</PRE
></FONT
></TD
></TR
></TABLE
></P
></DD
><DT
><B
CLASS="COMMAND"
>Alphabetizing a String</B
></DT
><DD
><P
>Alphabetize (in ASCII order) an arbitrary string
	      read from the command-line.</P
></DD
><DT
><B
CLASS="COMMAND"
>Parsing</B
></DT
><DD
><P
>Parse <A
HREF="files.html#DATAFILESREF1"
><TT
CLASS="FILENAME"
>/etc/passwd</TT
></A
>,
	    and output its contents in nice, easy-to-read tabular
	    form.</P
></DD
><DT
><B
CLASS="COMMAND"
>Logging Logins</B
></DT
><DD
><P
>Parse <TT
CLASS="FILENAME"
>/var/log/messages</TT
> to
	      produce a nicely formatted file of user logins and login
	      times. The script may need to run as
	      <I
CLASS="FIRSTTERM"
>root</I
>. (Hint: Search for the string
	      <SPAN
CLASS="QUOTE"
>"LOGIN."</SPAN
>)</P
></DD
><DT
><B
CLASS="COMMAND"
>Pretty-Printing a Data File</B
></DT
><DD
><P
>Certain database and spreadsheet packages use
	    save-files with the fields separated by commas, commonly
	    referred to as <I
CLASS="FIRSTTERM"
>comma-separated values</I
>
	    or CSVs. Other applications often need to parse these
	    files.</P
><P
>Given a data file with comma-separated
	    <A
HREF="special-chars.html#FIELDREF"
>fields</A
>, of the form:
	        <TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="90%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
>Jones,Bill,235 S. Williams St.,Denver,CO,80221,(303) 244-7989
Smith,Tom,404 Polk Ave.,Los Angeles,CA,90003,(213) 879-5612
...</PRE
></FONT
></TD
></TR
></TABLE
>
	      Reformat the data and print it out to
	      <TT
CLASS="FILENAME"
>stdout</TT
> in labeled, evenly-spaced columns.</P
></DD
><DT
><B
CLASS="COMMAND"
>Justification</B
></DT
><DD
><P
>Given ASCII text input either from
	      <TT
CLASS="FILENAME"
>stdin</TT
> or a file, adjust
	      the word spacing to right-justify each line to a
	      user-specified line-width, then send the output to
	      <TT
CLASS="FILENAME"
>stdout</TT
>.</P
></DD
><DT
><B
CLASS="COMMAND"
>Mailing List</B
></DT
><DD
><P
>Using the <A
HREF="communications.html#COMMMAIL1"
>mail</A
> command,
	      write a script that manages a simple mailing list. The
	      script automatically e-mails the monthly company newsletter,
	      read from a specified text file, and sends it to all the
	      addresses on the mailing list, which the script reads from
	      another specified file.</P
></DD
><DT
><B
CLASS="COMMAND"
>Generating Passwords</B
></DT
><DD
><P
>Generate pseudorandom 8-character passwords, using
	      characters in the ranges [0-9], [A-Z], [a-z]. Each password
	      must contain at least two digits.</P
></DD
><DT
><B
CLASS="COMMAND"
>Monitoring a User</B
></DT
><DD
><P
>You suspect that one particular user on the network
	      has been abusing her privileges and possibly attempting to
	      hack the system. Write a script to automatically monitor
	      and log her activities when she's signed on. The log file
	      will save entries for the previous week, and delete those
	      entries more than seven days old.</P
><P
>You may use <A
HREF="system.html#LASTREF"
>last</A
>,
             <A
HREF="system.html#LASTLOGREF"
>lastlog</A
>, and <A
HREF="system.html#LASTCOMMREF"
>lastcomm</A
> to aid your
             surveillance of the suspected fiend.</P
></DD
><DT
><B
CLASS="COMMAND"
>Checking for Broken Links</B
></DT
><DD
><P
>Using <A
HREF="communications.html#LYNXREF"
>lynx</A
> with the
	      <TT
CLASS="OPTION"
>-traversal</TT
> option, write a script that
	      checks a Web site for broken links.</P
></DD
></DL
></DIV
><P
></P
><DIV
CLASS="VARIABLELIST"
><P
><B
><A
NAME="EXDIFFICULT1"
></A
>DIFFICULT</B
></P
><DL
><DT
><B
CLASS="COMMAND"
>Testing Passwords</B
></DT
><DD
><P
>Write a script to check and validate passwords. The object
	      is to flag <SPAN
CLASS="QUOTE"
>"weak"</SPAN
> or easily guessed password
	      candidates.</P
><P
>A trial password will be input to the script as a
	       command-line parameter. To be considered acceptable,
	       a password must meet the following minimum qualifications:

            <P
></P
><UL
><LI
><P
>Minimum length of 8 characters</P
></LI
><LI
><P
>Must contain at least one numeric character</P
></LI
><LI
><P
>Must contain at least one of the following
		  non-alphabetic characters: <SPAN
CLASS="TOKEN"
>@</SPAN
>,
		  <SPAN
CLASS="TOKEN"
>#</SPAN
>, <SPAN
CLASS="TOKEN"
>$</SPAN
>, <SPAN
CLASS="TOKEN"
>%</SPAN
>,
		  <SPAN
CLASS="TOKEN"
>&#38;</SPAN
>, <SPAN
CLASS="TOKEN"
>*</SPAN
>, <SPAN
CLASS="TOKEN"
>+</SPAN
>,
		  <SPAN
CLASS="TOKEN"
>-</SPAN
>, <SPAN
CLASS="TOKEN"
>=</SPAN
></P
></LI
></UL
></P
><P
>Optional:

            <P
></P
><UL
><LI
><P
>Do a dictionary check on every sequence of at least
		  four consecutive alphabetic characters in the password under
		  test. This will eliminate passwords containing embedded
		  <SPAN
CLASS="QUOTE"
>"words"</SPAN
> found in a standard dictionary.</P
></LI
><LI
><P
>Enable the script to check all the passwords on your
		  system. These do not reside in
		  <A
HREF="files.html#DATAFILESREF1"
><TT
CLASS="FILENAME"
>/etc/passwd</TT
></A
>.</P
></LI
></UL
></P
><P
>This exercise tests mastery of <A
HREF="regexp.html#REGEXREF"
>Regular Expressions</A
>.</P
></DD
><DT
><B
CLASS="COMMAND"
>Cross Reference</B
></DT
><DD
><P
>Write a script that generates a
	      <I
CLASS="FIRSTTERM"
>cross-reference</I
>
	      (<I
CLASS="FIRSTTERM"
>concordance</I
>) on a target file.
	      The output will be a listing of all word occurrences in
	      the target file, along with the line numbers in which
	      each word occurs. Traditionally, <I
CLASS="FIRSTTERM"
>linked
	      list</I
> constructs would be used in such
	      applications. Therefore, you should investigate <A
HREF="arrays.html#ARRAYREF"
>arrays</A
> in the course of
	      this exercise. <A
HREF="textproc.html#WF"
>Example 16-12</A
> is probably
	      <EM
>not</EM
> a good place to start.</P
></DD
><DT
><A
NAME="NEWTONSQRT"
></A
><B
CLASS="COMMAND"
>Square Root</B
></DT
><DD
><P
>Write a script to calculate square roots of numbers
              using <I
CLASS="FIRSTTERM"
>Newton's Method</I
>.</P
><P
>The algorithm for this, expressed as a snippet of Bash
              <A
HREF="assortedtips.html#PSEUDOCODEREF"
>pseudo-code</A
> is:</P
><P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="90%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
>#  (Isaac) Newton's Method for speedy extraction
#+ of square roots.

guess = $argument
#  $argument is the number to find the square root of.
#  $guess is each successive calculated "guess" -- or trial solution --
#+ of the square root.
#  Our first "guess" at a square root is the argument itself.

oldguess = 0
# $oldguess is the previous $guess.

tolerance = .000001
# To how close a tolerance we wish to calculate.

loopcnt = 0
# Let's keep track of how many times through the loop.
# Some arguments will require more loop iterations than others.


while [ ABS( $guess $oldguess ) -gt $tolerance ]
#       ^^^^^^^^^^^^^^^^^^^^^^^ Fix up syntax, of course.

#      "ABS" is a (floating point) function to find the absolute value
#+      of the difference between the two terms.
#             So, as long as difference between current and previous
#+            trial solution (guess) exceeds the tolerance, keep looping.

do
   oldguess = $guess  # Update $oldguess to previous $guess.

#  =======================================================
   guess = ( $oldguess + ( $argument / $oldguess ) ) / 2.0
#        = 1/2 ( ($oldguess **2 + $argument) / $oldguess )
#  equivalent to:
#        = 1/2 ( $oldguess + $argument / $oldguess )
#  that is, "averaging out" the trial solution and
#+ the proportion of argument deviation
#+ (in effect, splitting the error in half).
#  This converges on an accurate solution
#+ with surprisingly few loop iterations . . .
#+ for arguments &#62; $tolerance, of course.
#  =======================================================

   (( loopcnt++ ))     # Update loop counter.
done</PRE
></FONT
></TD
></TR
></TABLE
></P
><P
>It's a simple enough recipe, and
              <EM
>seems</EM
> at first glance easy enough to
              convert into a working Bash script. The problem, though,
              is that Bash has <A
HREF="ops.html#NOFLOATINGPOINT"
>no native
              support for floating point numbers</A
>. So, the script
              writer needs to use <A
HREF="mathc.html#BCREF"
>bc</A
> or
              possibly <A
HREF="awk.html#AWKREF"
>awk</A
> to convert the
              numbers and do the calculations. It could get rather messy
              . . .</P
></DD
><DT
><B
CLASS="COMMAND"
>Logging File Accesses</B
></DT
><DD
><P
>Log all accesses to the files in <TT
CLASS="FILENAME"
>/etc</TT
> during the course of
	      a single day. This information should include the filename,
	      user name, and access time. If any alterations to the
	      files take place, that will be flagged. Write this data
	      as tabular (tab-separated) formatted records in a logfile.</P
></DD
><DT
><B
CLASS="COMMAND"
>Monitoring Processes</B
></DT
><DD
><P
>Write a script to continually monitor all running
	      processes and to keep track of how many child processes each
	      parent spawns. If a process spawns more than five children,
	      then the script sends an e-mail to the system administrator
	      (or <I
CLASS="FIRSTTERM"
>root</I
>) with all relevant
	      information, including the time, PID of the parent, PIDs
	      of the children, etc. The script appends a report to a log
	      file every ten minutes.  </P
></DD
><DT
><B
CLASS="COMMAND"
>Strip Comments</B
></DT
><DD
><P
>Strip all comments from a shell script whose name
              is specified on the command-line. Note that the initial
              <A
HREF="sha-bang.html#SHABANGREF"
>#! line</A
> must not be
              stripped out.</P
></DD
><DT
><B
CLASS="COMMAND"
>Strip HTML Tags</B
></DT
><DD
><P
>Strip all the HTML tags from a specified HTML file, then
	      reformat it into lines between 60 and 75 characters
	      in length. Reset paragraph and block spacing, as
	      appropriate, and convert HTML tables to their approximate
	      text equivalent.</P
></DD
><DT
><B
CLASS="COMMAND"
>XML Conversion</B
></DT
><DD
><P
>Convert an XML file to both HTML and text format.</P
><P
>Optional: A script that converts Docbook/SGML to XML.</P
></DD
><DT
><A
NAME="CSPAMMERS"
></A
><B
CLASS="COMMAND"
>Chasing Spammers</B
></DT
><DD
><P
> Write a script that analyzes a spam e-mail by doing
	      DNS lookups on the IP addresses in the headers to identify
	      the relay hosts as well as the originating ISP. The
	      script will forward the unaltered spam message to the
	      responsible ISPs. Of course, it will be necessary to
	      filter out <EM
>your own ISP's IP address</EM
>,
	      so you don't end up complaining about yourself.</P
><P
>As necessary, use the appropriate <A
HREF="communications.html#COMMUNINFO1"
>network analysis commands</A
>.</P
><P
>For some ideas, see <A
HREF="communications.html#ISSPAMMER"
>Example 16-41</A
> and <A
HREF="contributed-scripts.html#ISSPAMMER2"
>Example A-28</A
>.</P
><P
>Optional: Write a script that searches through a list of
	      e-mail messages and deletes the spam according to specified
	      filters.</P
></DD
><DT
><B
CLASS="COMMAND"
>Creating man pages</B
></DT
><DD
><P
>Write a script that automates the process of creating
	      <A
HREF="basic.html#MANREF"
>man pages</A
>.</P
><P
>Given a text file which contains information to be
	      formatted into a <I
CLASS="FIRSTTERM"
>man page</I
>, the
	      script will read the file, then invoke the appropriate
	      <A
HREF="textproc.html#GROFFREF"
>groff</A
> commands to
	      output the corresponding <I
CLASS="FIRSTTERM"
>man page</I
>
	      to <TT
CLASS="FILENAME"
>stdout</TT
>. The text file contains
	      blocks of information under the standard <I
CLASS="FIRSTTERM"
>man
	      page</I
> headings, i.e., NAME, SYNOPSIS,
	      DESCRIPTION, etc.</P
><P
><A
HREF="contributed-scripts.html#MANED"
>Example A-39</A
> is an instructive first step.</P
></DD
><DT
><B
CLASS="COMMAND"
>Hex Dump</B
></DT
><DD
><P
>Do a hex(adecimal) dump on a binary file
	      specified as an argument to the script. The output should
	      be in neat tabular <A
HREF="special-chars.html#FIELDREF"
>fields</A
>,
	      with the first field showing the address, each of the
	      next 8 fields a 4-byte hex number, and the final field
	      the ASCII equivalent of the previous 8 fields.</P
><P
>The obvious followup to this is to extend the hex
	      dump script into a disassembler. Using a lookup table,
	      or some other clever gimmick, convert the hex values into
	      80x86 op codes.</P
></DD
><DT
><B
CLASS="COMMAND"
>Emulating a Shift Register</B
></DT
><DD
><P
>Using <A
HREF="arrays.html#STACKEX"
>Example 27-15</A
> as an inspiration,
	      write a script that emulates a 64-bit shift register as
	      an <A
HREF="arrays.html#ARRAYREF"
>array</A
>. Implement
	      functions to <I
CLASS="FIRSTTERM"
>load</I
> the register,
	      <I
CLASS="FIRSTTERM"
>shift left</I
>, <I
CLASS="FIRSTTERM"
>shift
	      right</I
>, and <I
CLASS="FIRSTTERM"
>rotate</I
>
	      it. Finally, write a function that interprets the register
	      contents as eight 8-bit ASCII characters.</P
></DD
><DT
><B
CLASS="COMMAND"
>Calculating Determinants</B
></DT
><DD
><P
>Write a script that calculates
	      determinants
	        <A
NAME="AEN25254"
HREF="#FTN.AEN25254"
><SPAN
CLASS="footnote"
>[1]</SPAN
></A
>
		
		by <A
HREF="localvar.html#RECURSIONREF0"
>recursively</A
> expanding the
	      <I
CLASS="FIRSTTERM"
>minors</I
>. Use a 4 x 4 determinant as
	      a test case.</P
></DD
><DT
><B
CLASS="COMMAND"
>Hidden Words</B
></DT
><DD
><P
>Write a <SPAN
CLASS="QUOTE"
>"word-find"</SPAN
> puzzle generator,
	      a script that hides 10 input words in a 10 x 10 array
	      of random letters. The words may be hidden across, down,
	      or diagonally.</P
><P
>Optional: Write a script that <EM
>solves</EM
>
	      word-find puzzles. To keep this from becoming too difficult,
	      the solution script will find only horizontal and vertical
	      words. (Hint: Treat each row and column as a string, and
	      search for substrings.)</P
></DD
><DT
><B
CLASS="COMMAND"
>Anagramming</B
></DT
><DD
><P
> Anagram 4-letter input. For example, the
	      anagrams of <EM
>word</EM
> are:
	      <EM
>do or rod row word</EM
>. You may use
	      <TT
CLASS="FILENAME"
>/usr/share/dict/linux.words</TT
> as the
	      reference list.</P
></DD
><DT
><B
CLASS="COMMAND"
>Word Ladders</B
></DT
><DD
><P
>A <SPAN
CLASS="QUOTE"
>"word ladder"</SPAN
> is a sequence of words,
              with each successive word in the sequence differing from
              the previous one by a single letter.</P
><P
>For example, to <SPAN
CLASS="QUOTE"
>"ladder"</SPAN
> from
              <EM
>mark</EM
> to
              <EM
>vase</EM
>:</P
><P
>	    <TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="90%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
>mark --&#62; park --&#62; part --&#62; past --&#62; vast --&#62; vase
         ^           ^       ^      ^           ^</PRE
></FONT
></TD
></TR
></TABLE
>
            </P
><P
>Write a script that solves word ladder puzzles. Given
	      a starting and an ending word, the script will list all
	      intermediate steps in the <SPAN
CLASS="QUOTE"
>"ladder."</SPAN
> Note
	      that <EM
>all</EM
> words in the sequence must
	      be legitimate dictionary words.</P
></DD
><DT
><B
CLASS="COMMAND"
>Fog Index</B
></DT
><DD
><P
>The <SPAN
CLASS="QUOTE"
>"fog index"</SPAN
> of a passage of text
	      estimates its reading difficulty, as a number corresponding
	      roughly to a school grade level. For example, a passage
	      with a fog index of 12 should be comprehensible to anyone
	      with 12 years of schooling.</P
><P
>The Gunning version of the fog index uses the following
	      algorithm.</P
><P
></P
><OL
TYPE="1"
><LI
><P
>Choose a section of the text at least
                 100 words in length.</P
></LI
><LI
><P
>Count the number of sentences (a portion of
                 a sentence truncated by the boundary of the text section
                 counts as one).</P
></LI
><LI
><P
>Find the average number of words per
                   sentence.</P
><P
>AVE_WDS_SEN = TOTAL_WORDS / SENTENCES</P
></LI
><LI
><P
>Count the number of <SPAN
CLASS="QUOTE"
>"difficult"</SPAN
>
		   words in the segment -- those containing at least
		   3 syllables. Divide this quantity by total words to
		   get the proportion of difficult words.</P
><P
>PRO_DIFF_WORDS = LONG_WORDS / TOTAL_WORDS</P
></LI
><LI
><P
>The Gunning fog index is the sum of the above two
                   quantities, multiplied by 0.4, then rounded to the
                   nearest integer.</P
><P
>G_FOG_INDEX = int ( 0.4 * ( AVE_WDS_SEN  + PRO_DIFF_WORDS ) )</P
></LI
></OL
><P
>Step 4 is by far the most difficult portion of the
	      exercise. There exist various algorithms for estimating
	      the syllable count of a word. A rule-of-thumb formula
	      might consider the number of letters in a word and the
	      vowel-consonant mix.</P
><P
>A strict interpretation of the Gunning fog index does
	      not count compound words and proper nouns as
	      <SPAN
CLASS="QUOTE"
>"difficult"</SPAN
> words, but this would enormously
	      complicate the script.</P
></DD
><DT
><B
CLASS="COMMAND"
>Calculating PI using Buffon's Needle</B
></DT
><DD
><P
>The Eighteenth Century French mathematician de Buffon
	      came up with a novel experiment. Repeatedly drop a needle
	      of length <TT
CLASS="REPLACEABLE"
><I
>n</I
></TT
> onto a wooden floor
	      composed of long and narrow parallel boards. The cracks
	      separating the equal-width floorboards are a fixed distance
	      <TT
CLASS="REPLACEABLE"
><I
>d</I
></TT
> apart. Keep track of the
	      total drops and the number of times the needle intersects
	      a crack on the floor. The ratio of these two quantities
	      turns out to be a fractional multiple of PI.</P
><P
>In the spirit of <A
HREF="mathc.html#CANNON"
>Example 16-50</A
>, write a
	      script that runs a Monte Carlo simulation of
	      <I
CLASS="FIRSTTERM"
>Buffon's Needle</I
>. To simplify matters,
	      set the needle length equal to the distance between the
	      cracks, <TT
CLASS="PARAMETER"
><I
>n = d</I
></TT
>.</P
><P
>Hint: there are actually two critical variables:
              the distance from the center of the needle to the nearest
              crack, and the inclination angle of the needle to that crack.
	      You may use <A
HREF="mathc.html#BCREF"
>bc</A
> to handle
	      the calculations.</P
></DD
><DT
><B
CLASS="COMMAND"
>Playfair Cipher</B
></DT
><DD
><P
>Implement the Playfair (Wheatstone) Cipher in a
	      script.</P
><P
>The Playfair Cipher encrypts text by substitution
	      of <I
CLASS="FIRSTTERM"
>digrams</I
> (2-letter groupings).
	      It is traditional to use a 5 x 5 letter scrambled-alphabet
	      <I
CLASS="FIRSTTERM"
>key square</I
> for the encryption and
	      decryption.</P
><P
>	     <TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="90%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
>   C O D E S
   A B F G H
   I K L M N
   P Q R T U
   V W X Y Z

Each letter of the alphabet appears once, except "I" also represents
"J". The arbitrarily chosen key word, "CODES" comes first, then all
the rest of the alphabet, in order from left to right, skipping letters
already used.

To encrypt, separate the plaintext message into digrams (2-letter
groups). If a group has two identical letters, delete the second, and
form a new group. If there is a single letter left over at the end,
insert a "null" character, typically an "X."

THIS IS A TOP SECRET MESSAGE

TH IS IS AT OP SE CR ET ME SA GE



For each digram, there are three possibilities.
-----------------------------------------------

1) Both letters will be on the same row of the key square:
   For each letter, substitute the one immediately to the right, in that
   row. If necessary, wrap around left to the beginning of the row.

or

2) Both letters will be in the same column of the key square:
   For each letter, substitute the one immediately below it, in that
   row. If necessary, wrap around to the top of the column.

or

3) Both letters will form the corners of a rectangle within the key square:
   For each letter, substitute the one on the other corner the rectangle
   which lies on the same row.


The "TH" digram falls under case #3.
G H
M N
T U           (Rectangle with "T" and "H" at corners)

T --&#62; U
H --&#62; G


The "SE" digram falls under case #1.
C O D E S     (Row containing "S" and "E")

S --&#62; C  (wraps around left to beginning of row)
E --&#62; S

=========================================================================

To decrypt encrypted text, reverse the above procedure under cases #1
and #2 (move in opposite direction for substitution). Under case #3,
just take the remaining two corners of the rectangle.


Helen Fouche Gaines' classic work, ELEMENTARY CRYPTANALYSIS (1939), gives a
fairly detailed description of the Playfair Cipher and its solution methods.</PRE
></FONT
></TD
></TR
></TABLE
>
             </P
><P
>This script will have three main sections</P
><P
></P
><OL
TYPE="I"
><LI
><P
>Generating the <I
CLASS="FIRSTTERM"
>key square</I
>,
	         based on a user-input keyword.</P
></LI
><LI
><P
>Encrypting a <I
CLASS="FIRSTTERM"
>plaintext</I
>
	         message.</P
></LI
><LI
><P
>Decrypting encrypted
	         text.</P
></LI
></OL
><P
>The script will make extensive use of <A
HREF="arrays.html#ARRAYREF"
>arrays</A
> and <A
HREF="functions.html#FUNCTIONREF"
>functions</A
>.
               You may use <A
HREF="contributed-scripts.html#GRONSFELD"
>Example A-56</A
> as an
               inspiration.</P
></DD
></DL
></DIV
><P
>--</P
><P
>Please do not send the author your solutions to these
            exercises. There are more appropriate ways to impress him with
            your cleverness, such as submitting bugfixes and suggestions
            for improving the book.</P
></DIV
><H3
CLASS="FOOTNOTES"
>Notes</H3
><TABLE
BORDER="0"
CLASS="FOOTNOTES"
WIDTH="100%"
><TR
><TD
ALIGN="LEFT"
VALIGN="TOP"
WIDTH="5%"
><A
NAME="FTN.AEN25254"
HREF="writingscripts.html#AEN25254"
><SPAN
CLASS="footnote"
>[1]</SPAN
></A
></TD
><TD
ALIGN="LEFT"
VALIGN="TOP"
WIDTH="95%"
><P
>For all you clever types who failed intermediate algebra,
                a <I
CLASS="FIRSTTERM"
>determinant</I
> is a numerical value
		associated with a multidimensional
		<I
CLASS="FIRSTTERM"
>matrix</I
> (<A
HREF="arrays.html#ARRAYREF"
>array</A
> of numbers).
		  <TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="90%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
>For the simple case of a 2 x 2 determinant:

  |a  b|
  |b  a|

The solution is a*a - b*b, where "a" and "b" represent numbers.</PRE
></FONT
></TD
></TR
></TABLE
>
                </P
></TD
></TR
></TABLE
><DIV
CLASS="NAVFOOTER"
><HR
ALIGN="LEFT"
WIDTH="100%"><TABLE
SUMMARY="Footer navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
><A
HREF="scriptanalysis.html"
ACCESSKEY="P"
>Prev</A
></TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="index.html"
ACCESSKEY="H"
>Home</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
><A
HREF="revisionhistory.html"
ACCESSKEY="N"
>Next</A
></TD
></TR
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
>Analyzing Scripts</TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="exercises.html"
ACCESSKEY="U"
>Up</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
>Revision History</TD
></TR
></TABLE
></DIV
></BODY
></HTML
>